package gobase

import (
	"bytes"
	"context"
	"fmt"
	"gitee.com/ymofen/panicsafe"
	"log"
	"os"
	"os/signal"
	"runtime"
	"strconv"
	"time"
)

var (
	GoFunCatchException bool
	DeferCatchPanic     = panicsafe.DeferCatchPanic
)

// 三进制类型
type Ternary int8

const (
	TernaryFalse Ternary = iota - 1
	TernaryUnkown
	TernaryTrue
)

type LogLevel int8

const (
	LogDebug = iota + 1
	LogInfo
	LogWarning
	LogError
	LogSilent = 127
)

// 日志输出接口
type Logger interface {
	LogPrintf(lvl LogLevel, s string, args ...interface{})
}

type SysLogger struct {
	sender  string
	showlvl LogLevel
	skiplvl int
}

func (this *SysLogger) SetShowLvl(minlvl LogLevel) {
	this.showlvl = minlvl
}

func (this *SysLogger) LogPrintf(lvl LogLevel, s string, args ...interface{}) {
	if lvl < this.showlvl {
		return
	}
	logOutput(this.skiplvl, int8(lvl), this.sender, fmt.Sprintf(s, args...))
}

func LogPrintf(s string, args ...interface{}) {
	logOutput(2, LogInfo, "", fmt.Sprintf(s, args...))
}

func LogDebugPrintf(s string, args ...interface{}) {
	logOutput(2, LogDebug, "", fmt.Sprintf(s, args...))
}

func LogWarnPrintf(s string, args ...interface{}) {
	logOutput(2, LogWarning, "", fmt.Sprintf(s, args...))
}

func SetLogOutput(fn func(calldepth int, lvl int8, sender string, s string)) {
	if fn == nil {
		logOutput = defaultLogFunc
	} else {
		logOutput = fn
	}
}

var defaultLogFunc = func(calldepth int, lvl int8, sender string, s string) {
	log.Output(calldepth+1, s)
}

var logOutput = defaultLogFunc

func GetCurrentGoRoutineID() uint64 {
	b := make([]byte, 64)
	b = b[:runtime.Stack(b, false)]
	b = bytes.TrimPrefix(b, []byte("goroutine "))
	b = b[:bytes.IndexByte(b, ' ')]
	n, _ := strconv.ParseUint(string(b), 10, 64)
	return n
}

// 推荐这种方式，stack()，可以追寻的go的掉用处,
func GoRun(fn func()) {
	if panicsafe.GoFunCatchException {
		defer panicsafe.DeferCatchPanic()
	}
	fn()
}

func GoRunWithArgs(fn func(args ...interface{}), args ...interface{}) {
	if panicsafe.GoFunCatchException {
		defer panicsafe.DeferCatchPanic()
	}
	fn(args...)
}

var (
	DefaultLogger = &SysLogger{sender: "", skiplvl: 2}
)

func HookExitSignalAndInterval(runDuration time.Duration, interval time.Duration, intervalFn func()) int8 {
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, os.Kill)
	ti := time.NewTimer(interval)
	var runDurationCh <-chan time.Time
	if runDuration > 0 {
		runTicker := time.NewTicker(runDuration)
		runDurationCh = runTicker.C
	} else {
		runDurationCh = make(<-chan time.Time)
	}

	for {
		select {
		case <-c:
			return 0
		case <-runDurationCh:
			return 1
		case <-ti.C:
			intervalFn()
			ti.Reset(interval)
		}
	}
}

func RunInterval(runDuration time.Duration, interval time.Duration, intervalFn func()) {
	ti := time.NewTimer(interval)
	var runDurationCh <-chan time.Time
	if runDuration > 0 {
		runTicker := time.NewTicker(runDuration)
		runDurationCh = runTicker.C
	} else {
		runDurationCh = make(<-chan time.Time)
	}

	for {
		select {
		case <-runDurationCh:
			return
		case <-ti.C:
			intervalFn()
			ti.Reset(interval)
		}
	}
}

func RunContextAndInterval(ctx context.Context, interval time.Duration, intervalFn func()) {
	ti := time.NewTimer(interval)
	for {
		select {
		case _ = <-ctx.Done():
			return
		case <-ti.C:
			intervalFn()
			ti.Reset(interval)
		}
	}
}
